// -*-c++-*-

/*
 * 3DS reader/writer for Open Scene Graph
 *
 * Copyright (C) ???
 *
 * Writing support added 2009 by Sukender (Benoit Neil), http://sukender.free.fr,
 * strongly inspired by the OBJ writer object by Stephan Huber
 *
 * The Open Scene Graph (OSG) is a cross platform C++/OpenGL library for
 * real-time rendering of large 3D photo-realistic models.
 * The OSG homepage is http://www.openscenegraph.org/
 */


/// [EXPERIMENTAL] Disables animation data (and matrix transforms) for compatibility with some 3rd party apps.
/// Animations are not read by all 3DS importers. Thus disabling them may allow some 3rd-party apps, such as Rhinoceros (tested with 4.0) to correctly import 3DS files.
/// However, having proper hierarchy with matrix transforms will become impossible.
///\warning This is still experimental, hence the compile flag. This should become a reader/writer option as soon as it works as intended (maybe "noMatrixTransforms" could become a read/write option?).
#define DISABLE_3DS_ANIMATION 0            // Default = 0

#include <osg/io_utils>
#include <osg/CullFace>
#include <osg/Billboard>
#include <osgDB/WriteFile>

#include "WriterNodeVisitor.h"
#include <assert.h>
#include <string.h>


void copyOsgMatrixToLib3dsMatrix(Lib3dsMatrix lib3ds_matrix, const osg::Matrix &osg_matrix) {
    for (int row = 0; row < 4; ++row) {
        lib3ds_matrix[row][0] = osg_matrix.ptr()[row * 4 + 0];
        lib3ds_matrix[row][1] = osg_matrix.ptr()[row * 4 + 1];
        lib3ds_matrix[row][2] = osg_matrix.ptr()[row * 4 + 2];
        lib3ds_matrix[row][3] = osg_matrix.ptr()[row * 4 + 3];
    }
}

inline void copyOsgVectorToLib3dsVector(Lib3dsVector lib3ds_vector, const osg::Vec3f &osg_vector) {
    lib3ds_vector[0] = osg_vector[0];
    lib3ds_vector[1] = osg_vector[1];
    lib3ds_vector[2] = osg_vector[2];
}

inline void copyOsgVectorToLib3dsVector(Lib3dsVector lib3ds_vector, const osg::Vec3d &osg_vector) {
    lib3ds_vector[0] = osg_vector[0];
    lib3ds_vector[1] = osg_vector[1];
    lib3ds_vector[2] = osg_vector[2];
}

inline void copyOsgColorToLib3dsColor(Lib3dsVector lib3ds_vector, const osg::Vec4f &osg_vector) {
    lib3ds_vector[0] = osg_vector[0];
    lib3ds_vector[1] = osg_vector[1];
    lib3ds_vector[2] = osg_vector[2];
}

inline void copyOsgColorToLib3dsColor(Lib3dsVector lib3ds_vector, const osg::Vec4d &osg_vector) {
    lib3ds_vector[0] = osg_vector[0];
    lib3ds_vector[1] = osg_vector[1];
    lib3ds_vector[2] = osg_vector[2];
}

inline void copyOsgQuatToLib3dsQuat(float lib3ds_vector[4], const osg::Quat &osg_quat) {
    //lib3ds_vector[0] = osg_quat[3];        // Not sure
    //lib3ds_vector[1] = osg_quat[0];
    //lib3ds_vector[2] = osg_quat[1];
    //lib3ds_vector[3] = osg_quat[2];
    // 3DS seems to store (angle in radians, axis_x, axis_y, axis_z), but it works with (axis_x, axis_y, axis_z, -angle in radians)!
    osg::Quat::value_type angle, x, y, z;
    osg_quat.getRotate(angle, x, y, z);
    lib3ds_vector[0] = static_cast<float>(x);
    lib3ds_vector[1] = static_cast<float>(y);
    lib3ds_vector[2] = static_cast<float>(z);
    lib3ds_vector[3] = static_cast<float>(-angle);
}


/// Checks if a filename (\b not path) is 8.3 (an empty name is never 8.3, and a path is never 8.3).
/// Please note the '8' and '3' limitations are in \b bytes, not in characters (which is different when using UTF8).
bool is83(const std::string &s) {
    // 012345678901
    // ABCDEFGH.ABC
    if (s.find_first_of("/\\") != std::string::npos) return false;            // It should not be a path, but a filename
    unsigned int len = s.length();
    if (len > 12 || len == 0) return false;
    size_t pointPos = s.rfind('.');
    if (pointPos == std::string::npos) return len <= 8;        // Without point
    // With point
    if (pointPos > 8) return false;
    if (len - 1 - pointPos > 3) return false;
    return true;
}

inline std::string::size_type maxNameLen(bool extendedFilePaths, bool isNodeName) {
    if (extendedFilePaths) return 63;
    return isNodeName ? 8 : (8 + 1 + 3);
}

/// Tests if the given string is a path supported by 3DS format (8.3, 63 chars max, non empty).
inline bool is3DSName(const std::string &s, bool extendedFilePaths, bool isNodeName) {
    unsigned int len = s.length();
    if (len > maxNameLen(extendedFilePaths, isNodeName) || len == 0) return false;
    // Extended paths are simply those that fits the 64 bytes buffer.
    if (extendedFilePaths) return true;
    // Short paths simply must have no subdirectory.
    return is83(s);
}


// Use namespace qualification to avoid static-link symbol collitions
// from multiply defined symbols.
namespace plugin3ds {


/** writes all primitives of a primitive-set out to a stream, decomposes quads to triangles, line-strips to lines etc */
    class PrimitiveIndexWriter : public osg::PrimitiveIndexFunctor {
    public:
        PrimitiveIndexWriter(osg::Geometry *geo,
                             ListTriangle &listTriangles,
                             unsigned int drawable_n,
                             unsigned int material) :
                osg::PrimitiveIndexFunctor(),
                _drawable_n(drawable_n),
                _listTriangles(listTriangles),
                _modeCache(0),
                _hasNormalCoords(geo->getNormalArray() != NULL),
                _hasTexCoords(geo->getTexCoordArray(0) != NULL),
                _lastFaceIndex(0),
                _material(material) {
        }

        unsigned int getNextFaceIndex() { return _lastFaceIndex; }

        virtual void setVertexArray(unsigned int, const osg::Vec2 *) {}

        virtual void setVertexArray(unsigned int, const osg::Vec3 *) {}

        virtual void setVertexArray(unsigned int, const osg::Vec4 *) {}

        virtual void setVertexArray(unsigned int, const osg::Vec2d *) {}

        virtual void setVertexArray(unsigned int, const osg::Vec3d *) {}

        virtual void setVertexArray(unsigned int, const osg::Vec4d *) {}


        // operator for triangles
        void writeTriangle(unsigned int i1, unsigned int i2, unsigned int i3) {
            Triangle triangle;
            triangle.t1 = i1;
            triangle.t2 = i2;
            triangle.t3 = i3;
            triangle.material = _material;
            _listTriangles.push_back(ListTriangle::value_type(triangle, _drawable_n));
        }

        virtual void begin(GLenum mode) {
            _modeCache = mode;
            _indexCache.clear();
        }

        virtual void vertex(unsigned int vert) {
            _indexCache.push_back(vert);
        }

        virtual void end() {
            if (!_indexCache.empty()) {
                drawElements(_modeCache, _indexCache.size(), &_indexCache.front());
            }
        }

        virtual void drawArrays(GLenum mode, GLint first, GLsizei count);

        virtual void drawElements(GLenum mode, GLsizei count, const GLubyte *indices) {
            drawElementsImplementation<GLubyte>(mode, count, indices);
        }

        virtual void drawElements(GLenum mode, GLsizei count, const GLushort *indices) {
            drawElementsImplementation<GLushort>(mode, count, indices);
        }

        virtual void drawElements(GLenum mode, GLsizei count, const GLuint *indices) {
            drawElementsImplementation<GLuint>(mode, count, indices);
        }

    protected:

        template<typename T>
        void drawElementsImplementation(GLenum mode, GLsizei count, const T *indices) {
            if (indices == 0 || count == 0) return;

            typedef const T *IndexPointer;

            switch (mode) {
                case (GL_TRIANGLES): {
                    //lib3ds_mesh_resize_faces(_mesh, _lastFaceIndex + count / 3);
                    IndexPointer ilast = &indices[count];
                    for (IndexPointer iptr = indices; iptr < ilast; iptr += 3)
                        writeTriangle(*iptr, *(iptr + 1), *(iptr + 2));

                    break;
                }
                case (GL_TRIANGLE_STRIP): {
                    //lib3ds_mesh_resize_faces(_mesh, _lastFaceIndex + count -2);
                    IndexPointer iptr = indices;
                    for (GLsizei i = 2; i < count; ++i, ++iptr) {
                        if ((i % 2)) writeTriangle(*(iptr), *(iptr + 2), *(iptr + 1));
                        else writeTriangle(*(iptr), *(iptr + 1), *(iptr + 2));
                    }
                    break;
                }
                case (GL_QUADS): {
                    //lib3ds_mesh_resize_faces(_mesh, _lastFaceIndex + count /2);        // count/4*2
                    IndexPointer iptr = indices;
                    for (GLsizei i = 3; i < count; i += 4, iptr += 4) {
                        writeTriangle(*(iptr), *(iptr + 1), *(iptr + 2));
                        writeTriangle(*(iptr), *(iptr + 2), *(iptr + 3));
                    }
                    break;
                }
                case (GL_QUAD_STRIP): {
                    //lib3ds_mesh_resize_faces(_mesh, _lastFaceIndex + (count / 2 -1)*2);
                    IndexPointer iptr = indices;
                    for (GLsizei i = 3; i < count; i += 2, iptr += 2) {
                        writeTriangle(*(iptr), *(iptr + 1), *(iptr + 2));
                        writeTriangle(*(iptr + 1), *(iptr + 3), *(iptr + 2));
                    }
                    break;
                }
                case (GL_POLYGON): // treat polygons as GL_TRIANGLE_FAN
                case (GL_TRIANGLE_FAN): {
                    //lib3ds_mesh_resize_faces(_mesh, _lastFaceIndex + count -2);
                    IndexPointer iptr = indices;
                    unsigned int first = *iptr;
                    ++iptr;
                    for (GLsizei i = 2; i < count; ++i, ++iptr) {
                        writeTriangle(first, *(iptr), *(iptr + 1));
                    }
                    break;
                }
                case (GL_POINTS):
                case (GL_LINES):
                case (GL_LINE_STRIP):
                case (GL_LINE_LOOP):
                    // Not handled
                    break;

                default:
                    // uhm should never come to this point :)
                    break;
            }
        }

    private:

        PrimitiveIndexWriter &operator=(const PrimitiveIndexWriter &) { return *this; }

        unsigned int _drawable_n;
        ListTriangle &_listTriangles;
        GLenum _modeCache;
        std::vector<GLuint> _indexCache;
        bool _hasNormalCoords, _hasTexCoords;
        unsigned int _lastFaceIndex;
        unsigned int _material;
    };


    void PrimitiveIndexWriter::drawArrays(GLenum mode, GLint first, GLsizei count) {
        switch (mode) {
            case (GL_TRIANGLES): {
                unsigned int pos = first;
                for (GLsizei i = 2; i < count; i += 3, pos += 3) {
                    writeTriangle(pos, pos + 1, pos + 2);
                }
                break;
            }
            case (GL_TRIANGLE_STRIP): {
                unsigned int pos = first;
                for (GLsizei i = 2; i < count; ++i, ++pos) {
                    if ((i % 2)) writeTriangle(pos, pos + 2, pos + 1);
                    else writeTriangle(pos, pos + 1, pos + 2);
                }
                break;
            }
            case (GL_QUADS): {
                unsigned int pos = first;
                for (GLsizei i = 3; i < count; i += 4, pos += 4) {
                    writeTriangle(pos, pos + 1, pos + 2);
                    writeTriangle(pos, pos + 2, pos + 3);
                }
                break;
            }
            case (GL_QUAD_STRIP): {
                unsigned int pos = first;
                for (GLsizei i = 3; i < count; i += 2, pos += 2) {
                    writeTriangle(pos, pos + 1, pos + 2);
                    writeTriangle(pos + 1, pos + 3, pos + 2);
                }
                break;
            }
            case (GL_POLYGON): // treat polygons as GL_TRIANGLE_FAN
            case (GL_TRIANGLE_FAN): {
                unsigned int pos = first + 1;
                for (GLsizei i = 2; i < count; ++i, ++pos) {
                    writeTriangle(first, pos, pos + 1);
                }
                break;
            }
            case (GL_POINTS):
            case (GL_LINES):
            case (GL_LINE_STRIP):
            case (GL_LINE_LOOP):
                //break;
            default:
                OSG_WARN << "3DS WriterNodeVisitor: can't handle mode " << mode << std::endl;
                break;
        }
    }


    WriterNodeVisitor::Material::Material(WriterNodeVisitor &writerNodeVisitor, osg::StateSet *stateset,
                                          osg::Material *mat, osg::Texture *tex, bool preserveName, int ind) :
            index(ind),
            diffuse(1, 1, 1, 1),
            ambient(0.2, 0.2, 0.2, 1),
            specular(0, 0, 0, 1),
            shininess(0),
            transparency(0),
            double_sided(false),
            image(NULL),
            texture_transparency(false),
            texture_no_tile(true) // matches lib3ds_material.cpp initialize_texture_map(..) default flag setting
    {
        //static unsigned int s_objmaterial_id = 0;
        //++s_objmaterial_id;
        if (mat) {
            assert(stateset);
            diffuse = mat->getDiffuse(osg::Material::FRONT);
            ambient = mat->getAmbient(osg::Material::FRONT);
            specular = mat->getSpecular(osg::Material::FRONT);
            shininess = mat->getShininess(osg::Material::FRONT) / 128.f;
            // OpenGL shininess = pow(2, 10.0*mat->shininess);            (As in lib3ds example)
            // => mat->shininess = log.2( OpenGL shininess ) /10        (if values are >0)
            // => mat->shininess = log( OpenGL shininess ) / log(2) /10
            //shininess = mat->getShininess(osg::Material::FRONT) <= 0 ? 0 : log( mat->getShininess(osg::Material::FRONT) ) / log(2.f) / 10.f;

            transparency = 1 - diffuse.w();
            if (preserveName == false) {
                name = writerNodeVisitor.getUniqueName(mat->getName(), true, "mat");
            } else {
                name = writerNodeVisitor.getMaterialName(mat->getName());
            }
            osg::StateAttribute *attribute = stateset->getAttribute(osg::StateAttribute::CULLFACE);
            if (!attribute) {
                double_sided = true;
            } else {
                assert(dynamic_cast<osg::CullFace *>(attribute));
                osg::CullFace::Mode mode = static_cast<osg::CullFace *>(attribute)->getMode();
                if (mode == osg::CullFace::BACK) double_sided = false;
                else if (mode == osg::CullFace::FRONT) {
                    OSG_WARN << "3DS Writer: Reversed face (culled FRONT) not supported yet." << std::endl;
                    double_sided = false;
                } else {
                    assert(mode == osg::CullFace::FRONT_AND_BACK);
                    OSG_WARN << "3DS Writer: Invisible face (culled FRONT_AND_BACK) not supported yet." << std::endl;
                    double_sided = false;
                }
            }
        }
        if (tex) {
            osg::Image *img = tex->getImage(0);
            if (img) {
                texture_transparency = (stateset->getMode(GL_BLEND) == osg::StateAttribute::ON);
                osg::Texture::WrapMode wrapS = tex->getWrap(osg::Texture2D::WRAP_S);
                texture_no_tile = !(wrapS == osg::Texture2D::REPEAT || wrapS == osg::Texture2D::MIRROR);
                image = img;
            }
        }

        if (name.empty()) {
            std::stringstream ss;
            ss << "m" << index;
            name = ss.str();
        }
    }


/// Converts an extension to a 3-letters long one equivalent.
    std::string convertExt(const std::string &path, bool extendedFilePaths) {
        if (extendedFilePaths) return path;        // Extensions are not truncated for extended filenames

        std::string ext = osgDB::getFileExtensionIncludingDot(path);
        if (ext == ".tiff") ext = ".tif";
        else if (ext == ".jpeg") ext = ".jpg";
        else if (ext == ".jpeg2000" || ext == ".jpg2000") ext = ".jpc";
        return osgDB::getNameLessExtension(path) + ext;
    }


    WriterNodeVisitor::WriterNodeVisitor(Lib3dsFile *file3ds, const std::string &fileName,
                                         const osgDB::ReaderWriter::Options *options,
                                         const std::string &srcDirectory) :
            osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
            _succeeded(true),
            _srcDirectory(srcDirectory),
            _file3ds(file3ds),
            _currentStateSet(new osg::StateSet()),
            _lastMaterialIndex(0),
            _lastMeshIndex(0),
            _cur3dsNode(NULL),
            _options(options),
            _imageCount(0),
            _extendedFilePaths(false),
            _preserveMaterialNames(false) {
        if (!fileName.empty())
            _directory = options->getDatabasePathList().empty() ? osgDB::getFilePath(fileName)
                                                                : options->getDatabasePathList().front();

        if (options) {
            std::istringstream iss(options->getOptionString());
            std::string opt;
            while (iss >> opt) {
                if (opt == "extended3dsFilePaths" || opt == "extended3DSFilePaths")
                    _extendedFilePaths = true;
                if (opt == "preserveMaterialNames")
                    _preserveMaterialNames = true;
            }
        }
    }


    void WriterNodeVisitor::writeMaterials() {
        unsigned int nbMat = _materialMap.size();
        lib3ds_file_reserve_materials(_file3ds, nbMat, 1);
        // Ugly thing: it seems lib3ds_file_insert_material() doesn't support insertion in a random order (else materials are not assigned the right way)
        for (unsigned int iMat = 0; iMat < nbMat; ++iMat) {
            for (MaterialMap::iterator itr = _materialMap.begin(); itr != _materialMap.end(); ++itr) {
                const Material &mat = itr->second;
                if (mat.index != static_cast<int>(iMat)) continue;        // Ugly thing (2)

                Lib3dsMaterial *mat3ds = lib3ds_material_new(osgDB::getSimpleFileName(mat.name).c_str());
                copyOsgColorToLib3dsColor(mat3ds->ambient, mat.ambient);
                copyOsgColorToLib3dsColor(mat3ds->diffuse, mat.diffuse);
                copyOsgColorToLib3dsColor(mat3ds->specular, mat.specular);
                mat3ds->shininess = mat.shininess;
                mat3ds->transparency = mat.transparency;
                mat3ds->two_sided = mat.double_sided ? 1 : 0;
                if (mat.image) {
                    std::string path;
                    ImageSet::const_iterator itImage(_imageSet.find(mat.image.get()));
                    if (itImage != _imageSet.end()) {
                        // Image has been already used
                        path = itImage->second;
                    } else {
                        // First time we 'see' this image
                        if (mat.image->getFileName().empty()) {
                            std::ostringstream oss;
                            oss << "Image_" << _imageCount++ << ".rgb";
                            path = oss.str();
                        } else {
                            path = osgDB::concatPaths(_srcDirectory, mat.image->getFileName());
                            std::cout<< "\r\n" << mat.image->getFileName() << " file name image \r\n";
                        }

                        std::cout << "\r\n-------------ppp" << _srcDirectory << "pppp--------\r\n" << std::endl;

                        path = convertExt(path, _extendedFilePaths);

                        //std::cout << "\r\n-------------ppp convertExt " << path << "pppp--------\r\n" << std::endl;

                        //path = getUniqueName(path, false, "");

                        std::cout << "\r\n-------------ppp" << path << "pppp--------\r\n" << std::endl;
                        // Write
//                        const std::string fullPath(osgDB::concatPaths(_directory, path));

                        //std::cout << "\r\n-------------ppp--fullPath" << " " <<_directory << "  "<< fullPath << "pppp--------\r\n" << std::endl;

                        osgDB::makeDirectoryForFile(path);
                        osgDB::writeImageFile(*(mat.image), path, _options);

                        // Insert in map
                        _imageSet.insert(ImageSet::value_type(mat.image.get(), path));
                    }

                    Lib3dsTextureMap &tex = mat3ds->texture1_map;
                    osgDB::stringcopyfixedsize(tex.name, path.c_str());
                    // Here we don't assume anything about initial flags state (actually it is set to LIB3DS_TEXTURE_NO_TILE by lib3DS, but this is subject to change)
                    if (mat.texture_transparency) tex.flags |= LIB3DS_TEXTURE_ALPHA_SOURCE;
                    else tex.flags &= ~LIB3DS_TEXTURE_ALPHA_SOURCE;
                    if (mat.texture_no_tile) tex.flags |= LIB3DS_TEXTURE_NO_TILE;
                    else tex.flags &= ~LIB3DS_TEXTURE_NO_TILE;
                }
                if (!succeeded()) {
                    lib3ds_material_free(mat3ds);
                    return;
                }
                lib3ds_file_insert_material(_file3ds, mat3ds, itr->second.index);
                break;        // Ugly thing (3)
            }
        }
    }

/// Truncates an UTF8 string so that it does not takes more than a given \b bytes amount (\b excluding the potential NULL end character).
/// The function assumes the UTF8 string is valid.
///\return A valid UTF8 string which size is less or equal to \c byteLimit.
// May be moved in osgDB/ConvertUTF?
    std::string utf8TruncateBytes(const std::string &s, std::string::size_type byteLimit) {
        // Untruncated strings
        if (s.size() <= byteLimit) return s;

        // Truncated strings
        std::string::const_iterator it = s.begin(), itEnd = s.begin() + byteLimit;
        std::string::const_iterator itStop = it;
        // Note: itEnd is < s.end(), so that we can always write "it+1"
        for (; it != itEnd; ++it) {
            unsigned char c = static_cast<unsigned char>(*it);
            if ((c & 0x80) == 0) itStop = it + 1;        // 7 bits ANSI. itStop must then point after that character.
            else if ((c & 0x40) != 0)
                itStop = it;    // UTF8 sequence start: this is also past-the-end for the previous character (ANSI or UTF8)
        }
        return std::string(s.begin(), itStop);
    }

#ifdef OSG_USE_UTF8_FILENAME
#    define truncateFilenameBytes(str, size) utf8TruncateBytes(str, size)
#else
#    define truncateFilenameBytes(str, size) std::string(str, 0, size)
#endif

    std::string WriterNodeVisitor::getUniqueName(const std::string &_defaultValue, bool isNodeName,
                                                 const std::string &_defaultPrefix, int currentPrefixLen) {
        //const unsigned int MAX_LENGTH = maxNameLen(_extendedFilePaths);
        const unsigned int MAX_PREFIX_LENGTH = _extendedFilePaths ? 52
                                                                  : 6;        // Arbitrarily defined for short names, kept enough room for displaying UINT_MAX (10 characters) for long names.
        assert(_defaultPrefix.length() <= 4);        // Default prefix is too long (implementation error)
        const std::string defaultPrefix(_defaultPrefix.empty() ? "_" : _defaultPrefix);
        if (currentPrefixLen < 0) currentPrefixLen = osg::maximum(_defaultPrefix.length(), _defaultValue.length());
        currentPrefixLen = osg::clampBelow(currentPrefixLen, static_cast<int>(MAX_PREFIX_LENGTH));

        // Tests if default name is valid and unique
        NameMap &nameMap = isNodeName ? _nodeNameMap : _imageNameMap;
        PrefixMap &prefixMap = isNodeName ? _nodePrefixMap : _imagePrefixMap;

        // Handling of paths is simple. Algorithm:
        //    - For short names, subdirectories are simply forbidden. Use the simple file name.
        //    - Else, the whole (relative) path must simply be <64 chars.
        // After this, begin enumeration.
        std::string parentPath, filename, ext, namePrefix;
        unsigned int max_val = 0;

        // TODO Move the two parts of this giant if/else into two separate functions for better readability.
        if (_extendedFilePaths) {
            // Tests if default name is valid and unique
            if (is3DSName(_defaultValue, _extendedFilePaths, isNodeName)) {
                std::pair<NameMap::iterator, bool> insertion(nameMap.insert(_defaultValue));
                if (insertion.second) return _defaultValue;        // Return if element is newly inserted in the map (else there is a naming collision)
            }

            // Simply separate name and last extension
            filename = osgDB::getNameLessExtension(osgDB::getSimpleFileName(_defaultValue));
            if (!isNodeName) {
                ext = osgDB::getFileExtensionIncludingDot(_defaultValue);
                if (ext == ".") ext = "";
            }

            // Compute parent path
            // Pre-condition: paths are supposed to be relative.
            // If full path is too long (>MAX_PREFIX_LENGTH), cut path to let enough space for simple file name.
            // Do not cut in the middle of a name, but at path separators.
            parentPath = osgDB::getFilePath(_defaultValue);
            if (_defaultValue.length() > MAX_PREFIX_LENGTH)        // Not parentPath but _defaultValue!
            {
                // Nodes names: keep last directories (used only for the root name, generally named after the full file path)
                // Images names: keep first directories (for images)
                if (isNodeName) std::reverse(parentPath.begin(), parentPath.end());
                unsigned lenToDelete(filename.length() + ext.length() + 1);
                lenToDelete = osg::clampBelow(lenToDelete, MAX_PREFIX_LENGTH);
                parentPath = truncateFilenameBytes(parentPath,
                                                   MAX_PREFIX_LENGTH - lenToDelete);        // +1 for the path separator
                std::string::size_type separator = parentPath.find_last_of("/\\");
                if (separator != std::string::npos) parentPath = parentPath.substr(0, separator);
                if (isNodeName) std::reverse(parentPath.begin(), parentPath.end());
            }

            // Assert "MAX_PREFIX_LENGTH - parent path length - extension length -1" is >=0 and truncate name to this length to get our new prefix.
            assert(parentPath.length() + ext.length() <= MAX_PREFIX_LENGTH);
            const unsigned int len = MAX_PREFIX_LENGTH - (parentPath.length() + ext.length() + 1);
            namePrefix = truncateFilenameBytes(filename, len);
            if (namePrefix.empty()) namePrefix = defaultPrefix;

            // Truncate the filename to get our new prefix
            namePrefix = truncateFilenameBytes(filename, currentPrefixLen);

            // Enough space has been reserved for UINT_MAX values
            max_val = UINT_MAX;
        } else {
            // Get last extension, and make filename have no extension
            filename = osgDB::getNameLessAllExtensions(osgDB::getSimpleFileName(_defaultValue));
            if (!isNodeName) {
                ext = truncateFilenameBytes(osgDB::getFileExtensionIncludingDot(_defaultValue),
                                            4);        // 4 chars = dot + 3 chars
                if (ext == ".") ext = "";
            }

            // Tests if STRIPPED default name is valid and unique
            const std::string strippedName(filename + ext);
            if (is3DSName(strippedName, _extendedFilePaths, isNodeName)) {
                std::pair<NameMap::iterator, bool> insertion(nameMap.insert(strippedName));
                if (insertion.second) return strippedName;        // Return if element is newly inserted in the map (else there is a naming collision)
            }

            namePrefix = filename;
            if (namePrefix.empty()) namePrefix = defaultPrefix;
            // Truncate the filename to get our new prefix
            namePrefix = truncateFilenameBytes(namePrefix, currentPrefixLen);

            // Compute the maximum enumeration value
            max_val = static_cast<unsigned int>(pow(10., 8. - namePrefix.length())) - 1;
        }
        assert(namePrefix.size() <= MAX_PREFIX_LENGTH);

        // Find the current enumeration value (searchStart)
        unsigned int searchStart(0);
        PrefixMap::iterator pairPrefix(prefixMap.find(namePrefix));
        if (pairPrefix != prefixMap.end()) {
            searchStart = pairPrefix->second;
        } else {
            // Check if truncated name is ok
            const std::string res(osgDB::concatPaths(parentPath, namePrefix + ext));
            if (nameMap.find(res) == nameMap.end()) {
                prefixMap.insert(std::pair<std::string, unsigned int>(namePrefix, 0));
                nameMap.insert(res);
                return res;
            }
        }

        // Search for a free value
        for (unsigned int i = searchStart; i <= max_val; ++i) {
            std::stringstream ss;
            ss << namePrefix << i;
            const std::string res(osgDB::concatPaths(parentPath, ss.str() + ext));
            if (nameMap.find(res) == nameMap.end()) {
                if (pairPrefix != prefixMap.end()) {
                    pairPrefix->second = i + 1;
                } else {
                    prefixMap.insert(std::pair<std::string, unsigned int>(namePrefix, i + 1));
                }
                nameMap.insert(res);
                return res;
            }
        }

        // Failed finding a name
        // Try with a shorter prefix if possible
        if (currentPrefixLen > 1) return getUniqueName(_defaultValue, isNodeName, defaultPrefix, currentPrefixLen - 1);
        // Try with default prefix if not arleady done
        if (defaultPrefix != std::string("_")) return getUniqueName(_defaultValue, isNodeName, "_", 1);

        // No more names
        OSG_NOTIFY(osg::FATAL) << "No more names available!" << std::endl;
        _succeeded = false;
        return "ERROR";
    }

    std::string WriterNodeVisitor::getMaterialName(const std::string &inputMaterialName) {
        std::string result;

        // Search in map if this material name already exists
        MaterialNameMap::const_iterator mapIter = _materialNameMap.find(inputMaterialName);

        if (mapIter != _materialNameMap.end()) {
            // Found, use it !
            result = mapIter->second;
        } else {
            // Not found, create a new (unique) entry mapped to this material name
            std::string resultPrefix = inputMaterialName.substr(0,
                                                                60);    // Up to 64 characters : truncate to 60 characters : 60 + "_" + number
            result = resultPrefix;

            NameMap::const_iterator setIter = _materialNameSet.find(result);
            int suffixValue = -1;
            while (setIter != _materialNameSet.end()) {
                if (suffixValue < 0) {
                    // First add of the suffix
                    resultPrefix = resultPrefix + "_";
                    suffixValue++;
                }

                std::stringstream ss;
                ss << resultPrefix << suffixValue;

                result = ss.str();

                setIter = _materialNameSet.find(result);
                suffixValue++;
            }

            // Add entry in map
            _materialNameMap[inputMaterialName] = result;
        }

        return result;
    }

    int WriterNodeVisitor::processStateSet(osg::StateSet *ss) {
        MaterialMap::const_iterator itr = _materialMap.find(ss);
        if (itr != _materialMap.end()) {
            assert(itr->second.index >= 0);
            return itr->second.index;
        }

        osg::Material *mat = dynamic_cast<osg::Material *>(ss->getAttribute(osg::StateAttribute::MATERIAL));
        osg::Texture *tex = dynamic_cast<osg::Texture *>(ss->getTextureAttribute(0, osg::StateAttribute::TEXTURE));

        if (mat || tex) {
            int matNum = _lastMaterialIndex;
            _materialMap.insert(std::make_pair(osg::ref_ptr<osg::StateSet>(ss),
                                               Material(*this, ss, mat, tex, _preserveMaterialNames, matNum)));
            ++_lastMaterialIndex;
            return matNum;
        }
        return -1;
    }

/**
*  Add a vertice to the index and link it with the Triangle index and the drawable.
*  \param index_vert is the map where the vertice are stored.
*  \param index is the indice of the vertice's position in the vec3.
*  \param drawable_n is the number of the drawable.
*  \return the position of the vertice in the final mesh.
*/
    unsigned int
    WriterNodeVisitor::getMeshIndexForGeometryIndex(MapIndices &index_vert,
                                                    unsigned int index,
                                                    unsigned int drawable_n) {
        MapIndices::iterator itIndex = index_vert.find(std::pair<unsigned int, unsigned int>(index, drawable_n));
        if (itIndex == index_vert.end()) {
            unsigned int indexMesh = index_vert.size();
            index_vert.insert(std::make_pair(std::pair<unsigned int, unsigned int>(index, drawable_n), indexMesh));
            return indexMesh;
        }
        return itIndex->second;
    }


    void
    WriterNodeVisitor::buildMesh(osg::Geode &geo,
                                 const osg::Matrix &mat,
                                 MapIndices &index_vert,
                                 bool texcoords,
                                 Lib3dsMesh *mesh) {
        OSG_DEBUG << "Building Mesh" << std::endl;
        assert(mesh);

        // Write points
        assert(index_vert.size() <= MAX_VERTICES);
        lib3ds_mesh_resize_vertices(mesh, index_vert.size(), texcoords ? 1 : 0, 0);

        for (MapIndices::iterator it = index_vert.begin(); it != index_vert.end(); ++it) {
            osg::Geometry *g = geo.getDrawable(it->first.second)->asGeometry();
            const osg::Array *basevecs = g->getVertexArray();
            assert(basevecs);
            if (!basevecs || basevecs->getNumElements() == 0) continue;
            if (basevecs->getType() == osg::Array::Vec3ArrayType) {
                const osg::Vec3Array &vecs = *static_cast<const osg::Vec3Array *>(basevecs);
                copyOsgVectorToLib3dsVector(mesh->vertices[it->second], vecs[it->first.first] * mat);
            } else if (basevecs->getType() == osg::Array::Vec3dArrayType) {
                // Handle double presision vertices by converting them to float with a warning
                OSG_NOTICE
                    << "3DS format only supports single precision vertices. Converting double precision to single."
                    << std::endl;
                const osg::Vec3dArray &vecs = *static_cast<const osg::Vec3dArray *>(basevecs);
                copyOsgVectorToLib3dsVector(mesh->vertices[it->second], vecs[it->first.first] * mat);
            } else {
                OSG_NOTIFY(osg::FATAL) << "Vertex array is not Vec3 or Vec3d. Not implemented" << std::endl;
                _succeeded = false;
                return;
            }
        }

        // Write texture coords (Texture 0 only)
        if (texcoords) {
            for (MapIndices::iterator it = index_vert.begin(); it != index_vert.end(); ++it) {
                osg::Geometry *g = geo.getDrawable(it->first.second)->asGeometry();
                const osg::Array *texarray = g->getNumTexCoordArrays() >= 1 ? g->getTexCoordArray(0) : NULL;
                if (!texarray || texarray->getNumElements() == 0) continue;

                if (g->getTexCoordArray(0)->getType() != osg::Array::Vec2ArrayType) {
                    OSG_NOTIFY(osg::FATAL) << "Texture coords array is not Vec2. Not implemented" << std::endl;
                    _succeeded = false;
                    return;
                }
                const osg::Vec2Array &vecs = *static_cast<const osg::Vec2Array *>(texarray);
                mesh->texcos[it->second][0] = vecs[it->first.first][0];
                mesh->texcos[it->second][1] = vecs[it->first.first][1];
            }
        }
        lib3ds_file_insert_mesh(_file3ds, mesh, _lastMeshIndex);
        ++_lastMeshIndex;

        Lib3dsMeshInstanceNode *node3ds = lib3ds_node_new_mesh_instance(mesh, mesh->name, NULL, NULL, NULL);
        lib3ds_file_append_node(_file3ds, reinterpret_cast<Lib3dsNode *>(node3ds),
                                reinterpret_cast<Lib3dsNode *>(_cur3dsNode));
    }

    unsigned int
    WriterNodeVisitor::calcVertices(osg::Geode &geo) {
        unsigned int numVertice = 0;
        for (unsigned int i = 0; i < geo.getNumDrawables(); ++i) {
            osg::Geometry *g = geo.getDrawable(i)->asGeometry();
            if (g != NULL && g->getVertexArray()) numVertice += g->getVertexArray()->getNumElements();
        }
        return numVertice;
    }


    void
    WriterNodeVisitor::buildFaces(osg::Geode &geo,
                                  const osg::Matrix &mat,
                                  ListTriangle &listTriangles,
                                  bool texcoords) {
        unsigned int nbTrianglesRemaining = listTriangles.size();
        unsigned int nbVerticesRemaining = calcVertices(geo);        // May set _succeded to false
        if (!succeeded()) return;

        std::string name(getUniqueName(geo.getName().empty() ? geo.className() : geo.getName(), true, "geo"));
        if (!succeeded()) return;
        Lib3dsMesh *mesh = lib3ds_mesh_new(name.c_str());
        if (!mesh) {
            OSG_NOTIFY(osg::FATAL) << "Allocation error" << std::endl;
            _succeeded = false;
            return;
        }

        //copyOsgMatrixToLib3dsMatrix(mesh->matrix, mat);
        lib3ds_mesh_resize_faces(mesh, osg::minimum(nbTrianglesRemaining, MAX_FACES));
        lib3ds_mesh_resize_vertices(mesh, osg::minimum(nbVerticesRemaining, MAX_VERTICES), texcoords ? 0 : 1,
                                    0);        // Not mandatory but will allocate once a big block

        // Test if the mesh will be split and needs sorting
        if (nbVerticesRemaining >= MAX_VERTICES || nbTrianglesRemaining >= MAX_FACES) {
            OSG_INFO << "Sorting elements..." << std::endl;
            WriterCompareTriangle cmp(geo, nbVerticesRemaining);
            std::sort(listTriangles.begin(), listTriangles.end(), cmp);
        }

        MapIndices index_vert;
        unsigned int numFace = 0;        // Current face index
        for (ListTriangle::iterator it = listTriangles.begin();
             it != listTriangles.end(); ++it) //Go through the triangle list to define meshs
        {
            // Test if the mesh will be full after adding a face
            if (index_vert.size() + 3 >= MAX_VERTICES || numFace + 1 >= MAX_FACES) {
                // Finnish mesh
                lib3ds_mesh_resize_faces(mesh, numFace);
                //lib3ds_mesh_resize_vertices() will be called in buildMesh()
                buildMesh(geo, mat, index_vert, texcoords, mesh);        // May set _succeded to false
                if (!succeeded()) {
                    lib3ds_mesh_free(mesh);
                    return;
                }

                // "Reset" values and start over a new mesh
                index_vert.clear();
                nbTrianglesRemaining -= numFace;
                numFace = 0;
                // We can't call a thing like "nbVerticesRemaining -= ...;" because points may be used multiple times.
                // [Sukender: An optimisation here would take too much time I think.]

                mesh = lib3ds_mesh_new(
                        getUniqueName(geo.getName().empty() ? geo.className() : geo.getName(), true, "geo").c_str());
                if (!mesh) {
                    OSG_NOTIFY(osg::FATAL) << "Allocation error" << std::endl;
                    _succeeded = false;
                    return;
                }
                lib3ds_mesh_resize_faces(mesh, osg::minimum(nbTrianglesRemaining, MAX_FACES));
                lib3ds_mesh_resize_vertices(mesh, osg::minimum(nbVerticesRemaining, MAX_VERTICES), texcoords ? 0 : 1,
                                            0);        // Not mandatory but will allocate once a big block
            }
            Lib3dsFace &face = mesh->faces[numFace++];
            face.index[0] = getMeshIndexForGeometryIndex(index_vert, it->first.t1, it->second);
            face.index[1] = getMeshIndexForGeometryIndex(index_vert, it->first.t2, it->second);
            face.index[2] = getMeshIndexForGeometryIndex(index_vert, it->first.t3, it->second);
            face.material = it->first.material;
        }

        buildMesh(geo, mat, index_vert, texcoords, mesh);        // May set _succeded to false
        if (!succeeded()) {
            lib3ds_mesh_free(mesh);
            return;
        }
    }

    void
    WriterNodeVisitor::createListTriangle(osg::Geometry *geo,
                                          ListTriangle &listTriangles,
                                          bool &texcoords,
                                          unsigned int &drawable_n) {
        const osg::Array *basevecs = geo->getVertexArray();
        if (!basevecs || basevecs->getNumElements() == 0) return;

        // Texture coords
        const osg::Array *basetexvecs = geo->getNumTexCoordArrays() >= 1 ? geo->getTexCoordArray(0) : NULL;
        if (basetexvecs) {
            unsigned int nb = basetexvecs->getNumElements();
            if (nb != geo->getVertexArray()->getNumElements()) {
                OSG_NOTIFY(osg::FATAL) << "There are more/less texture coords than vertices (corrupted geometry)"
                                       << std::endl;
                _succeeded = false;
                return;
            }
            texcoords = true;
        }

        int material = processStateSet(_currentStateSet.get());

        for (unsigned int i = 0; i < geo->getNumPrimitiveSets(); ++i) //Fill the Triangle List
        {
            osg::PrimitiveSet *ps = geo->getPrimitiveSet(i);
            PrimitiveIndexWriter pif(geo, listTriangles, drawable_n, material);
            ps->accept(pif);
        }
    }

    void WriterNodeVisitor::apply(osg::Geode &node) {
        pushStateSet(node.getStateSet());
        //_nameStack.push_back(node.getName());
        unsigned int count = node.getNumDrawables();
        ListTriangle listTriangles;
        bool texcoords = false;
        for (unsigned int i = 0; i < count; i++) {
            osg::Geometry *g = node.getDrawable(i)->asGeometry();
            if (g != NULL) {
                pushStateSet(g->getStateSet());
                createListTriangle(g, listTriangles, texcoords, i);        // May set _succeded to false
                popStateSet(g->getStateSet());
                if (!succeeded()) break;
            }
        }
        if (succeeded() && count > 0) {
#if DISABLE_3DS_ANIMATION
            osg::Matrix mat( osg::computeLocalToWorld(getNodePath()) );
            buildFaces(node, mat, listTriangles, texcoords);        // May set _succeded to false
#else
            buildFaces(node, osg::Matrix(), listTriangles, texcoords);        // May set _succeded to false
#endif
        }
        popStateSet(node.getStateSet());
        //_nameStack.pop_back();
        if (succeeded())
            traverse(node);
    }

    void WriterNodeVisitor::apply(osg::Billboard &node) {
        // TODO Does not handle Billboards' points yet

        pushStateSet(node.getStateSet());
        Lib3dsMeshInstanceNode *parent = _cur3dsNode;

        unsigned int count = node.getNumDrawables();
        ListTriangle listTriangles;
        bool texcoords = false;
        OSG_NOTICE << "Warning: 3DS writer is incomplete for Billboards (rotation not implemented)." << std::endl;
#if DISABLE_3DS_ANIMATION
        osg::Matrix m( osg::computeLocalToWorld(getNodePath()) );
#endif
        for (unsigned int i = 0; i < count; i++) {
            osg::Geometry *g = node.getDrawable(i)->asGeometry();
            if (g != NULL) {
                listTriangles.clear();
                _cur3dsNode = parent;

                pushStateSet(g->getStateSet());
                createListTriangle(g, listTriangles, texcoords, i);
                popStateSet(g->getStateSet());        // May set _succeded to false
                if (!succeeded()) break;

                osg::Matrix pointLocalMat(osg::Matrix::translate(node.getPosition(i)));        // TODO handle rotation
#if DISABLE_3DS_ANIMATION
                osg::Matrix currentBillboardWorldMat(pointLocalMat * m);
                apply3DSMatrixNode(node, &pointLocalMat, "bil");                            // Add a 3DS matrix node (with local matrix)
                buildFaces(node, currentBillboardWorldMat, listTriangles, texcoords);        // May set _succeded to false
#else
                apply3DSMatrixNode(node, &pointLocalMat,
                                   "bil");                            // Add a 3DS matrix node (with local matrix)
                buildFaces(node, osg::Matrix(), listTriangles,
                           texcoords);                    // May set _succeded to false
#endif
                if (!succeeded()) break;
            }
        }

        if (succeeded())
            traverse(node);
        _cur3dsNode = parent;
        popStateSet(node.getStateSet());
    }


    void WriterNodeVisitor::apply(osg::Group &node) {
        pushStateSet(node.getStateSet());
        Lib3dsMeshInstanceNode *parent = _cur3dsNode;
#if DISABLE_3DS_ANIMATION
        osg::Matrix mat( osg::computeLocalToWorld(getNodePath()) );
        apply3DSMatrixNode(node, &mat, "grp");
#else
        apply3DSMatrixNode(node, NULL, "grp");
#endif
        if (succeeded())
            traverse(node);
        _cur3dsNode = parent;
        popStateSet(node.getStateSet());
    }

    void WriterNodeVisitor::apply(osg::MatrixTransform &node) {
        pushStateSet(node.getStateSet());
        Lib3dsMeshInstanceNode *parent = _cur3dsNode;
#if DISABLE_3DS_ANIMATION
        osg::Matrix mat( osg::computeLocalToWorld(getNodePath()) );
#else
        osg::Matrix mat(node.getMatrix());
#endif
        apply3DSMatrixNode(node, &mat, "mtx");
        if (succeeded())
            traverse(node);
        _cur3dsNode = parent;
        popStateSet(node.getStateSet());
    }

    void WriterNodeVisitor::apply3DSMatrixNode(osg::Node &node, const osg::Matrix *m, const char *const prefix) {
        // Note: Creating a mesh instance with no transform and then copying the matrix doesn't work (matrix seems to be a temporary/computed value)
        Lib3dsMeshInstanceNode *parent = _cur3dsNode;
        Lib3dsMeshInstanceNode *node3ds = NULL;
        if (m) {
            osg::Vec3 osgScl, osgPos;
            osg::Quat osgRot, osgSo;
            m->decompose(osgPos, osgRot, osgScl, osgSo);

            float pos[3];
            float scl[3];
            float rot[4];
            copyOsgVectorToLib3dsVector(pos, osgPos);
            copyOsgVectorToLib3dsVector(scl, osgScl);
            copyOsgQuatToLib3dsQuat(rot, osgRot);
            node3ds = lib3ds_node_new_mesh_instance(NULL, getUniqueName(
                    node.getName().empty() ? node.className() : node.getName(), true, prefix).c_str(), pos, scl, rot);
        } else {
            node3ds = lib3ds_node_new_mesh_instance(NULL, getUniqueName(
                    node.getName().empty() ? node.className() : node.getName(), true, prefix).c_str(), NULL, NULL,
                                                    NULL);
        }

        lib3ds_file_append_node(_file3ds, reinterpret_cast<Lib3dsNode *>(node3ds),
                                reinterpret_cast<Lib3dsNode *>(parent));
        _cur3dsNode = node3ds;
    }

// end namespace plugin3ds
}
